import static com.googlecode.javacv.cpp.opencv_core.CV_32FC1;
import static com.googlecode.javacv.cpp.opencv_core.CV_32SC1;
import static com.googlecode.javacv.cpp.opencv_core.CV_STORAGE_READ;
import static com.googlecode.javacv.cpp.opencv_core.CV_STORAGE_WRITE;
import static com.googlecode.javacv.cpp.opencv_core.CV_TERMCRIT_ITER;
import static com.googlecode.javacv.cpp.opencv_core.IPL_DEPTH_32F;
import static com.googlecode.javacv.cpp.opencv_core.cvAttrList;
import static com.googlecode.javacv.cpp.opencv_core.cvCreateImage;
import static com.googlecode.javacv.cpp.opencv_core.cvCreateMat;
import static com.googlecode.javacv.cpp.opencv_core.cvOpenFileStorage;
import static com.googlecode.javacv.cpp.opencv_core.cvReadByName;
import static com.googlecode.javacv.cpp.opencv_core.cvReadIntByName;
import static com.googlecode.javacv.cpp.opencv_core.cvReleaseFileStorage;
import static com.googlecode.javacv.cpp.opencv_core.cvTermCriteria;
import static com.googlecode.javacv.cpp.opencv_core.cvWrite;
import static com.googlecode.javacv.cpp.opencv_core.cvWriteInt;
import static com.googlecode.javacv.cpp.opencv_highgui.CV_LOAD_IMAGE_GRAYSCALE;
import static com.googlecode.javacv.cpp.opencv_highgui.cvLoadImage;
import static com.googlecode.javacv.cpp.opencv_highgui.cvSaveImage;
import static com.googlecode.javacv.cpp.opencv_legacy.CV_EIGOBJ_NO_CALLBACK;
import static com.googlecode.javacv.cpp.opencv_legacy.cvCalcEigenObjects;
import static com.googlecode.javacv.cpp.opencv_legacy.cvEigenDecomposite;
import com.sun.jna.ptr.PointerByReference;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.nio.FloatBuffer;
import java.sql.Savepoint;
import java.util.Scanner;

import com.googlecode.javacpp.FloatPointer;
import com.googlecode.javacpp.IntPointer;
import com.googlecode.javacpp.Loader;
import com.googlecode.javacpp.Pointer;
import com.googlecode.javacpp.PointerPointer;
import com.googlecode.javacv.cpp.opencv_legacy;
import com.googlecode.javacv.cpp.opencv_objdetect;
import com.googlecode.javacv.cpp.opencv_core.CvAllocFunc;
import com.googlecode.javacv.cpp.opencv_core.CvFileStorage;
import com.googlecode.javacv.cpp.opencv_core.CvMat;
import com.googlecode.javacv.cpp.opencv_core.CvMatND;
import com.googlecode.javacv.cpp.opencv_core.CvSize;
import com.googlecode.javacv.cpp.opencv_core.CvTermCriteria;
import com.googlecode.javacv.cpp.opencv_core.IplImage;

public class Eignfaces {

	//global variables
	int nTrainFaces=0;					// number of training images
	int nEigens=0; 						//number of eigenvalues
	IplImage[] faceImgArr=null;			//array of face images	
	CvMat personNumTruthMat=null; 		//array of person numbers
	IplImage pAvgTrainImg=null;			//the average image
	IplImage[] eigenVectArr=null;		//igenvectors
	CvMat eigenValMat=null;				//eigenvalues
	CvMat projectedTrainFaceMAt= null; 	//projected training faces
	
	CvMat[] ARRAYprojectedTrainFaceMAt= null; 	//collection of projected training faces
	
	CvMat trainPersonNumMat= null; 	//projected training faces
	//mio
	Pointer[] pointersEigenVectArr;
	Pointer[] pointersFaceImgArr;
	
	
	boolean IF_DEBUG=false;
	public static void main(String[] args) {
		
		Eignfaces e=new Eignfaces();
		
		try {
			e.learn();
			e.recognize();
		} catch (FileNotFoundException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		
		
		if (args.length !=2) {
			printUsage();
			return;
		}
		
		if (args[1].equals("train")) {
			//learn();
		}
		else if (args[1].equals("test")) {
			//recognize();
		}
		else{
			System.err.println("Unknown command");
			printUsage();
		}
	}
	
	
	private boolean loadTrainingData(CvMat pTrainPersonNumMat) {
		
		CvFileStorage fileStorage;
		
		fileStorage=cvOpenFileStorage("facedata.xml", null,CV_STORAGE_READ);
			
		if (fileStorage.isNull()) {
			System.err.println("Can't open facedata.xml");
			return false;
		}
		
		nEigens=cvReadIntByName(fileStorage, null,"nEigens",0);
		
		nTrainFaces=cvReadIntByName(fileStorage, null, "nTrainFaces",0);
		
		
		Pointer p= cvReadByName(fileStorage, null, "trainPersonNumMat", null);
		
		pTrainPersonNumMat=new CvMat(p);
		
		eigenValMat=new CvMat(cvReadByName(fileStorage, null, "eigenValMat", null));
		
		projectedTrainFaceMAt=new CvMat(cvReadByName(fileStorage, null, "projectedTrainFaceMat", null));
		
		pAvgTrainImg=new IplImage(cvReadByName(fileStorage, null, "avgTrainImg", null));
		
		eigenVectArr=new IplImage[nTrainFaces];
		
		for (int i = 0; i <nEigens; i++) {
			String varname;
			varname="eigenVect_"+i;
			eigenVectArr[i]=new IplImage(cvReadByName(fileStorage, null, varname, null));
		}
		
		cvReleaseFileStorage(fileStorage);
		
		return true;
	}
	
	public void recognize() throws FileNotFoundException {
		int i,nTestFaces=0;
//		CvMat trainPersonNumMAtCvMat=null;
		float[] projectedTestFace=null;
		
		nTestFaces=loadFaceImgArray2("text.txt");
		
		System.out.println(nTestFaces+" test faces loaded");
		
//		if (!loadTrainingData(trainPersonNumMAtCvMat))return;
//		trainPersonNumMAtCvMat=personNumTruthMat;
		
		projectedTestFace=new float[nEigens];
		
		pointersFaceImgArr=getPointersFrom(faceImgArr);
		pointersEigenVectArr=getPointersFrom(eigenVectArr);
		
//		Pointer[] pointersFaceImgArr=getPointersFrom(faceImgArr);
//		Pointer[] pointersEigenVectArr=getPointersFrom(eigenVectArr);
		
		for (int j = 0; j < nTestFaces; j++) {
			
			
			int iNearest,nearest,truth;
//			cvEigenDecomposite(faceImgArr[i], nEigens, new PointerPointer(pointersEigenVectArr), 0,		 null	, pAvgTrainImg, projectedTrainFaceMAt.getFloatBuffer());
			//project the test image onto PCA subspace
			//					//IplImage	//int	  //pointer									int		pointer	Iplimage	float[]
//			CvMat matTEST =cvCreateMat(nTrainFaces, nEigens, CV_32FC1);
//			cvEigenDecomposite(faceImgArr[j], nEigens, new PointerPointer(pointersEigenVectArr), 0, 	null,	 pAvgTrainImg, matTEST.getFloatBuffer());
			cvEigenDecomposite(faceImgArr[j], nEigens, new PointerPointer(pointersEigenVectArr), 0, 	null,	 pAvgTrainImg, projectedTestFace);
			
			if (IF_DEBUG) {
				printArray(projectedTestFace);
			}
		
			iNearest=findNearestNeighbor(projectedTestFace);
			truth= (int) trainPersonNumMat.get(j);
			nearest=(int) trainPersonNumMat.get(iNearest);
//			System.out.printf("iNearest=%d,nearest= %d, Truth= %d\n",iNearest,nearest,truth);
			System.out.printf("nearest= %d, Truth= %d\n",nearest,truth);
			
		}
		
		
	}
	
	private void printArray(float[] a) {
		
		for (int i = 0; i < a.length; i++) {
			System.out.printf("a[%d]=%f\n",i,a[i]);
		}
		
	}
	private void printArray(double[] a) {
		
		for (int i = 0; i < a.length; i++) {
			System.out.printf("a[%d]=%f\n",i,a[i]);
		}
		
	}
	private int findNearestNeighbor(float[] projectedTestFace) {
		// TODO Auto-generated method stub

		
		double leastDistSq=Integer.MAX_VALUE;
		
		int iTrain,INearest=0;
		
		for ( iTrain = 0; iTrain <nTrainFaces; iTrain++) {
			
			double distSq=0;
//			CvMatND actual=projectedTrainFaceMAt.data_fl(new FloatPointer(new PointerPointer(1)));
//			actual.
			for (int i = 0; i < nEigens; i++) {
				//nrows		//ncols		//datatype
//				projectedTrainFaceMAt =cvCreateMat(nTrainFaces, nEigens, CV_32FC1);
//				double projectedInfo=projectedTrainFaceMAt.get(0, i);
//				double projectedInfo=ARRAYprojectedTrainFaceMAt[iTrain].get(0, i);
				double projectedInfo=ARRAYprojectedTrainFaceMAt[iTrain].get(i);
				
				if (IF_DEBUG) {
					System.out.println("projectedTrainFaceMAt[0,"+i+"]="+projectedInfo);
				}
				
//				double asdf=projectedTrainFaceMAt.get((iTrain*nEigens+i));
//				double asdf=projectedTrainFaceMAt.get(i);
//				double asdf2=projectedTrainFaceMAt.get(iTrain*nEigens+i);
////				double asdf3=projectedTrainFaceMAt.get((iTrain*nEigens+i)+2);
//				double asdf4=projectedTrainFaceMAt.get(iTrain);
//				double asdf5=projectedTrainFaceMAt.get(2);
//				double[] array=projectedTrainFaceMAt.get();
////				double[] array=actual.get();
//				printArray(array);
//				FloatBuffer fb= projectedTrainFaceMAt.getFloatBuffer();
//				FloatBuffer fb= actual.getFloatBuffer();
//				System.out.println(fb);
//				float[] f=fb.array();
//				printArray(f);
//				FloatBuffer fb=projectedTrainFaceMAt.getFloatBuffer();
//				printArray(fb.array());
//				System.out.println(fb.get());
//				CvMatND result=projectedTrainFaceMAt.data_fl(new FloatPointer((float)iTrain*nEigens+i));
////				FloatPointer fp=result.data_fl();
//				System.out.println(projectedTrainFaceMAt.data_fl(new FloatPointer((float)iTrain*nEigens+i)));
//				CvMat actual=projectedTrainFaceMAt.get(iTrain, nEigens);
//				float d_i= (float) (projectedTestFace[i]-array[i]);
				float d_i= (float) (projectedTestFace[i]-projectedInfo);
				
				distSq+=d_i*d_i;
				
			}
			
			if (distSq<leastDistSq) {
				leastDistSq=distSq;
				
				INearest=iTrain;
				
			}
			
		}
		
		return INearest;
	}
	
	public void learn() {
		try {
			nTrainFaces=loadFaceImgArray("train.txt");
			
			if (nTrainFaces<2) {
				
				System.err.println("Need  2 or more training faces\n"+
						"Need 2 or more training faces\n"+
						"Input file contains only "+nTrainFaces);
				return;
			}
			
			
			ARRAYprojectedTrainFaceMAt=new CvMat[nTrainFaces];
			
			doPCA();
												//nrows		//ncols		//datatype
			projectedTrainFaceMAt =cvCreateMat(nTrainFaces, nEigens, CV_32FC1);
//			FloatBuffer buffer=projectedTrainFaceMAt.getFloatBuffer();
			
			
			pointersFaceImgArr=getPointersFrom(faceImgArr);
			pointersEigenVectArr=getPointersFrom(eigenVectArr);
			
			//borrame
			float[] projectedTestFace=new float[nEigens];
			
			for (int i = 0; i < nTrainFaces; i++) {

				projectedTrainFaceMAt =cvCreateMat(nTrainFaces, nEigens, CV_32FC1);
		//							IplImage		int		pointer								int		pointer		IplImage		FloatBuffer
				cvEigenDecomposite(faceImgArr[i], nEigens, new PointerPointer(pointersEigenVectArr), 0,		 null	, pAvgTrainImg, projectedTrainFaceMAt.getFloatBuffer());
				ARRAYprojectedTrainFaceMAt[i]=projectedTrainFaceMAt;
				if (IF_DEBUG) {
					System.out.println("projectedTrainFaceMAt="+projectedTrainFaceMAt);
					for (int j = 0; j < nEigens; j++) {
						System.out.println("projectedTrainFaceMAt[0,"+j+"]="+projectedTrainFaceMAt.get(0, j));
					}
				
					cvEigenDecomposite(faceImgArr[i], nEigens, new PointerPointer(pointersEigenVectArr), 0,		 null	, pAvgTrainImg, projectedTestFace);
					printArray(projectedTestFace);
				}
				
//				float[] asdf=new float[nEigens];
//				cvEigenDecomposite(faceImgArr[i], nEigens, new PointerPointer(pointersEigenVectArr), 0,		 null	, pAvgTrainImg,asdf);
//				for (float f : asdf) {
//					System.out.println(f);
//				}
//				cvEigenDecomposite(faceImgArr[i], nEigens, new Pointer(eigenVectArr[i]), 0,		 null	, pAvgTrainImg, projectedTrainFaceMAt.getFloatBuffer());
//				cvEigenDecomposite(faceImgArr[i], nEigens, new PointerPointer(eigenVectArr[i]), 0,		 null	, pAvgTrainImg, projectedTrainFaceMAt.getFloatBuffer());
//				cvEigenDecomposite(faceImgArr[i], nEigens, new PointerPointer(eigenVectArr[i]), 0,		 null	, pAvgTrainImg, buffer.array());
				
			}
			
//			System.out.println(projectedTrainFaceMAt);
			
//			for (float a : projectedTrainFaceMAt.getFloatBuffer().array()) {
//				System.out.println(a);
//			}
			
			//storeTrainingData();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	
	private void storeTrainingData() {
		CvFileStorage fileStorage;
		
		fileStorage=cvOpenFileStorage("facedata.xml", null, CV_STORAGE_WRITE);
		
		cvWriteInt(fileStorage, "nEigens", nEigens);
		cvWriteInt(fileStorage, "nTrainFaces", nTrainFaces);
		cvWrite(fileStorage, "trainPersonNumMat", personNumTruthMat,cvAttrList(null, null));
		cvWrite(fileStorage, "eigenValMat", eigenValMat,cvAttrList(null, null));
		cvWrite(fileStorage, "projectedTrainFaceMAt", projectedTrainFaceMAt,cvAttrList(null, null));
		cvWrite(fileStorage, "pAvgTrainImg", pAvgTrainImg,cvAttrList(null, null));
		
		for (int i= 0; i <nEigens; i++) {
			String varname="eigenVect_"+i;
			cvWrite(fileStorage, varname, eigenVectArr[i], cvAttrList(null, null));
		}
		cvReleaseFileStorage(fileStorage);
	}
	
	
	private int loadFaceImgArray2(String filename) throws FileNotFoundException {

		int nFaces=0;
		Scanner sc=new Scanner(new FileReader(filename));
		
		String imgFileName;
		
		while (sc.hasNextLine()) {
			nFaces++;
			sc.nextLine();
		}
		sc.close();

		sc=new Scanner(new FileReader(filename));
		
		if (!IF_DEBUG) 
		faceImgArr=new IplImage[nFaces];
		
		trainPersonNumMat= cvCreateMat(1,nFaces,CV_32SC1);
		
		for (int iFace = 0; iFace <nFaces; iFace++) {
			int id=sc.nextInt();
			imgFileName=sc.next();
//			personNumTruthMat.put(personNumTruthMat.position()+iFace, id);
			//FIXME
			//NOTSURE
			trainPersonNumMat.put(iFace, id);
//			personNumTruthMat.data_i(new Intp)
//			IntPointer ip= personNumTruthMat.data_i();
//			IntPointer ip2=ip.put(iFace);
			
			if (IF_DEBUG) {
//				System.out.println("position="+personNumTruthMat.position());
				System.out.println("trainPerson["+iFace+"]="+trainPersonNumMat.get(iFace));

			}
			else
				faceImgArr[iFace]=cvLoadImage(imgFileName, CV_LOAD_IMAGE_GRAYSCALE);
			
		}
		sc.close();
		
//		for (int i = 0; i < faceImgArr.length; i++) {
//			cvSaveImage(i+".pgm", faceImgArr[i]);
//		}
		
		return nFaces;
	}
	private int loadFaceImgArray(String filename) throws FileNotFoundException {

		int nFaces=0;
		Scanner sc=new Scanner(new FileReader(filename));
		
		String imgFileName;
		
		while (sc.hasNextLine()) {
			nFaces++;
			sc.nextLine();
		}
		sc.close();

		sc=new Scanner(new FileReader(filename));
		
		faceImgArr=new IplImage[nFaces];
		
		personNumTruthMat= cvCreateMat(1,nFaces,CV_32SC1);
		
		for (int iFace = 0; iFace <nFaces; iFace++) {
			int id=sc.nextInt();
			imgFileName=sc.next();
//			personNumTruthMat.put(personNumTruthMat.position()+iFace, id);
			//FIXME
			//NOTSURE
			personNumTruthMat.put(iFace, id);
//			personNumTruthMat.data_i(new Intp)
//			IntPointer ip= personNumTruthMat.data_i();
//			IntPointer ip2=ip.put(iFace);
		if (IF_DEBUG) {
		//	System.out.println("position="+personNumTruthMat.position());
			System.out.println("person["+iFace+"]="+personNumTruthMat.get(iFace));
		}
		faceImgArr[iFace]=cvLoadImage(imgFileName, CV_LOAD_IMAGE_GRAYSCALE);	
			
		}
		sc.close();
		
//		for (int i = 0; i < faceImgArr.length; i++) {
//			cvSaveImage(i+".pgm", faceImgArr[i]);
//		}
		
		return nFaces;
	}
	
	private static void printUsage() {
	System.out.println("Usage: igenface <command>\n Valid commands are\ntrain\ntest\n");

	}
	
	private void doPCA() {
		
		CvTermCriteria calcLimit;
		CvSize faceImgSize = new CvSize();
		
		//set the number of eigenvalues to use
		nEigens=nTrainFaces-1;
		
		//allocate the eigenvector images
		faceImgSize=faceImgSize.width(faceImgArr[0].width());
		faceImgSize=faceImgSize.height(faceImgArr[0].height());
		eigenVectArr=new IplImage[nEigens];
		
		for (int i = 0; i < eigenVectArr.length; i++) {
//eigenVectArr[i]=cvCreateImage(faceImgSize, IPL_DEPTH_32F, 1);
//			eigenVectArr[i]=new IplImage();
//			eigenVectArr[i].imageData(cvCreateImage(faceImgSize, IPL_DEPTH_32F, 1).imageData());
			eigenVectArr[i]=cvCreateImage(faceImgSize, IPL_DEPTH_32F, 1);
//			cvSaveImage(i+".png", eigenVectArr[i]);
		}
		
		//allocate the eigenvalues array
		eigenValMat=cvCreateMat(1, nEigens, CV_32FC1);
//		double[] b=eigenValMat.get();
//		System.out.println(b);
//		b=new double[1];
		//allocate the averaged image
		pAvgTrainImg = cvCreateImage(faceImgSize, IPL_DEPTH_32F, 1);
		
		//set the PCA termination criterion
		calcLimit= cvTermCriteria(CV_TERMCRIT_ITER, nEigens, 1);
		
//		PointerByReference p=new PointerByReference();
//		com.sun.jna.Pointer asd=new com.sun.jna.Pointer;
//		p.setPointer(asd);
//		PointerByReference p=new PointerByReference(new Pointer((new PointerPointer(faceImgArr)));
//		= new PointerByReference(value)
//		cvCalcEigenObjects(nTrainFaces	, new PointerPointer(faceImgArr), new PointerPointer(eigenVectArr),CV_EIGOBJ_NO_CALLBACK,0, 0,calcLimit,pAvgTrainImg,eigenValMat.getFloatBuffer());
		
		pointersFaceImgArr=getPointersFrom(faceImgArr);
		pointersEigenVectArr=getPointersFrom(eigenVectArr);
		
		if (false && IF_DEBUG) {
			System.out.println(eigenValMat.toString(2));  
			FloatBuffer fbs=eigenValMat.getFloatBuffer();
			System.out.println(fbs);	
			for (int i = 0; i < eigenVectArr.length; i++) {
				cvSaveImage("eigenVectArr"+i+".jpg",eigenVectArr[i] );
			}
		}
		
		cvCalcEigenObjects(nTrainFaces	, new PointerPointer(pointersFaceImgArr), new PointerPointer(pointersEigenVectArr),CV_EIGOBJ_NO_CALLBACK,0,null,calcLimit,pAvgTrainImg,eigenValMat.getFloatBuffer());
		
//		for (int i = 0; i < eigenVectArr.length; i++) {
//			cvSaveImage("eigenVectArr"+i+".jpg",eigenVectArr[i] );
//		}
		
//		cvSaveImage("avgimage.jpg",pAvgTrainImg );
//		cvCalcEigenObjects(nTrainFaces	, new PointerPointer(faceImgArr).get(), new PointerPointer(eigenVectArr).get(),CV_EIGOBJ_NO_CALLBACK,0,null,calcLimit,pAvgTrainImg,eigenValMat.getFloatBuffer());
//		cvCalcEigenObjects(nTrainFaces	,new Pointer(), new Pointer(new PointerPointer(eigenVectArr)),CV_EIGOBJ_NO_CALLBACK,0, null,calcLimit,pAvgTrainImg,eigenValMat.getFloatBuffer());
//		new IplImage.PointerByReference(faceImgArr).getPointer();
//		eigenVectArr[i].
		if (false && IF_DEBUG) {
			System.out.println(eigenValMat.toString(2));
			FloatBuffer fb=eigenValMat.getFloatBuffer();
			System.out.println(fb);	
		}
		
//		float[] b=eigenValMat.getFloatBuffer().array();
//		for (float a : eigenValMat.getFloatBuffer().array()) {
//			System.out.println(a);
//		}
		
	}
	private Pointer[] getPointersFrom(IplImage[] some) {
		Pointer[] pointers=new Pointer[some.length];
		
		for (int i = 0; i < pointers.length; i++) {
			pointers[i]=new Pointer(some[i]);
		}
		return pointers;
	}
}
